﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Eneta.Portal.Common.Definitions;
using Eneta.Portal.Common.Dto;
using Eneta.Portal.Common.Helpers;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;

namespace Eneta.Portal.Common.Repository
{
    /// <summary>
    /// Repository for announcements.
    /// </summary>
    public class AnnouncementRepository : NewsItemRepository
    {
        private readonly string _announcementsSiteUrl;

        /// <summary>
        /// Initializes a new instance of the <see cref="AnnouncementRepository"/> class.
        /// </summary>
        //public AnnouncementRepository(SPWeb web) : base(web)
        //{
       
        //}
        public AnnouncementRepository(string announcementsSiteUrl) : base(null)
        {
            _announcementsSiteUrl = announcementsSiteUrl;
        }

        /// <summary>
        /// Approves the announcement.
        /// </summary>
        /// <param name="id">The id of announcement to approve.</param>
        /// <param name="comment">The comment by moderator.</param>
        public void ApproveAnnouncement(int id, string comment)
        {
            var sec = new SPSecurity.CodeToRunElevated(delegate
                  {
                      using (var site = new SPSite(_announcementsSiteUrl))
                      using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                      {
                          SetAnnouncementStatus(web, id, SPModerationStatusType.Approved,  comment);
                      }
                  }                                         
                );
            SPSecurity.RunWithElevatedPrivileges(sec);
        }

        /// <summary>
        /// Denies the announcement.
        /// </summary>
        /// <param name="id">The id of announcement.</param>
        /// <param name="comment">The comment by moderator.</param>
        public void DenyAnnouncement(int id, string comment)
        {
            var sec = new SPSecurity.CodeToRunElevated(delegate
            {
                using (var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    SetAnnouncementStatus(web, id, SPModerationStatusType.Denied, comment);
                }
            });
            SPSecurity.RunWithElevatedPrivileges(sec);
        }

        /// <summary>
        /// Gets the announcements RSS.
        /// </summary>
        /// <returns>Returns announcements RSS as string.</returns>
        public string GetAnnouncementsRss()
        {
            var items = GetApprovedAnnouncements(1, 25);

            var title = "Eneta portaal: Teated";
            var url = "http://www.eneta.ee/uudised/teated/";

            return CreateRss(title, url, items.Results);
        }

        /// <summary>
        /// Gets announcement comments RSS as string.
        /// </summary>
        /// <returns>
        /// Returns announcement comments RSS as string.
        /// </returns>
        public new string GetCommentsRss()
        {
            var rss = string.Empty;

            var sec = new SPSecurity.CodeToRunElevated(delegate
                           {
                               using(var site = new SPSite(_announcementsSiteUrl))
                               using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                               {
                                   rss = GetCommentsRss(web);
                               }

                           });
            SPSecurity.RunWithElevatedPrivileges(sec);

            return rss;
        }

        /// <summary>
        /// Gets the comments list of comments for all announcements.
        /// </summary>
        /// <param name="limit">The number of comments to return. 0 means return all.</param>
        public IList<CommentDto> GetComments(int limit)
        {
            IList<CommentDto> result = new List<CommentDto>();

            var sec = new SPSecurity.CodeToRunElevated(delegate
            {
                using (var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    result = GetComments(web, limit);
                }

            });
            SPSecurity.RunWithElevatedPrivileges(sec);

            return result;
        }

        private IList<CommentDto> GetComments(SPWeb web, int limit)
        {
            var list = web.Lists["Comments"];
            var queryString = "<OrderBy><FieldRef Name='Created' Ascending='False' /></OrderBy>";
            var query = new SPQuery
            {
                Query = queryString,
                RowLimit = 50
            };

            var items = list.GetItems(query);
            var comments = CreateComments(items, true).Take(limit).ToList();

            return comments;
        }

        private string GetCommentsRss(SPWeb web)
        {
            var list = web.Lists["Comments"];
            var queryString = "<OrderBy><FieldRef Name='Created' Ascending='False' /></OrderBy>";
            var query = new SPQuery
            {
                Query = queryString,
                RowLimit = 50
            };

            var items = list.GetItems(query);
            var comments = CreateComments(items, true).Take(25).ToList();

            var title = "Eneta portaal: Teadete kommentaarid";
            var url = "http://www.eneta.ee/uudised/teated/";

            return CreateCommentsRss(title, url, comments);
        }

        private IList<CommentDto> CreateComments(SPListItemCollection items, bool excludeUnapproved)
        {
            IList<CommentDto> comments = new List<CommentDto>();

            foreach(SPListItem item in items)
            {
                var comment = CreateComment(item);
                if(excludeUnapproved)
                    if (comment.Parent.Status != SPModerationStatusType.Approved)
                        continue;
                comments.Add(comment);
            }

            return comments;
        }

        private CommentDto CreateComment(SPListItem item)
        {
            if (item == null)
                return null;

            AnnouncementDto announcement = null;
            
            if(item["NewsItem"] != null)
            {
                var annValue = new SPFieldLookupValue(item["NewsItem"].ToString());
                announcement = GetAnnouncementById(annValue.LookupId);
            }

            var comment = CreateComment(announcement, item);
            return comment;
        }

        private string CreateRss(string title, string viewUrl, IEnumerable<AnnouncementDto> entries)
        {
            var builder = new StringBuilder(GetRssHeader(title, viewUrl));
            if (entries == null)
            {
                builder.Append(GetRssFooter());
                return builder.ToString();
            }

            foreach (var entry in entries)
            {
                if (entry == null)
                    continue;
                builder.Append("<item>");
                builder.Append("<title>");
                if (!string.IsNullOrEmpty(entry.Title))
                    builder.Append(entry.Title.Replace("&", "&amp;"));
                builder.Append("</title>\r\n");

                builder.Append("<link>");
                builder.Append("http://www.eneta.ee/uudised/teated/Lehed/" + entry.FileName);
                builder.Append("</link>\r\n");

                builder.Append("<comments>");
                builder.Append("http://www.eneta.ee/uudised/teated/Lehed/");
                builder.Append(entry.FileName);
                builder.Append("#comments</comments>\r\n");

                builder.Append("<pubDate>");
                builder.Append(entry.Created.ToString("r"));
                builder.Append("</pubDate>\r\n");

                builder.Append("<dc:creator>");
                builder.Append(entry.OwnerUserName);
                builder.Append("</dc:creator>\r\n");

                builder.Append("<guid isPermaLink=\"false\">");
                builder.Append("http://www.eneta.ee/uudised/teated/Lehed/" + entry.FileName);
                builder.Append("</guid>\r\n");

                builder.Append("<content:encoded><![CDATA[");
                builder.Append(entry.Body);
                builder.Append("]]></content:encoded>\r\n");

                builder.Append("<slash:comments>");
                builder.Append(entry.Comments.Count);
                builder.Append("</slash:comments>");

                builder.Append("</item>");
            }

            builder.Append(GetRssFooter());
            return builder.ToString();
        }

        private static void SetAnnouncementStatus(SPWeb web, int id, SPModerationStatusType status, string comment)
        {
            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var list = pubWeb.PagesList;
            var item = list.GetItemById(id);

            if (item == null)
                return;
            if (item.ModerationInformation == null)
                return;

            item.ModerationInformation.Status = status;
            item.ModerationInformation.Comment = comment;
            item.Update();
        }

        /// <summary>
        /// Gets the public announcement by id.
        /// </summary>
        /// <param name="id">The id of announcement.</param>
        public AnnouncementDto GetPublicAnnouncementById(int id)
        {
            var dto = GetAnnouncementById(id);
            if (dto.Status != SPModerationStatusType.Approved)
                return null;

            return dto;
        }

        /// <summary>
        /// Gets the announcement by id.
        /// </summary>
        /// <param name="id">The id of announcement.</param>
        public new AnnouncementDto GetAnnouncementById(int id)
        {
            AnnouncementDto dto = null;

            var sec = new SPSecurity.CodeToRunElevated(delegate
                  {
                      using (var site = new SPSite(_announcementsSiteUrl))
                      using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                      {
                          dto = GetAnnouncementById(web, id);
                      }
                  }
                );
            SPSecurity.RunWithElevatedPrivileges(sec);

            return dto;
        }

        private AnnouncementDto GetAnnouncementById(SPWeb web, int id)
        {
            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var list = pubWeb.PagesList;
            var item = list.GetItemById(id);
            return CreateAnnouncement(item);
        }

        /// <summary>
       /// Gets the announcements related to company with given id.
       /// </summary>
       /// <param name="companyId">The company id.</param>
       /// <param name="onlyApprovedAnnouncements">if set to <c>true</c> returns only approved announcements.</param>
        public IList<AnnouncementDto> GetAnnouncementsByCompany(int companyId, bool onlyApprovedAnnouncements)
        {
            IList<AnnouncementDto> result = null;

            var sec = new SPSecurity.CodeToRunElevated(delegate
            {
                using (var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    result = GetAnnouncementsByCompany(web, companyId, onlyApprovedAnnouncements);
                }
            }
                );
            SPSecurity.RunWithElevatedPrivileges(sec);

            return result;
        }
        private  IList<AnnouncementDto> GetAnnouncementsByCompany(SPWeb web, int companyId, bool onlyApprovedAnnouncements)
        {
            var queryString = "<Where><Eq><FieldRef Name='Companies' LookupId='True' /><Value Type='Lookup'>{0}</Value></Eq></Where>";
            queryString += "<OrderBy><FieldRef Name='Created' Ascending='False' /></OrderBy>";
            queryString = string.Format(queryString, companyId);

            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var list = pubWeb.PagesList;
            var items = ExecuteQuery(list, queryString);

            var result = CreateAnnouncements(items);

            if(onlyApprovedAnnouncements)
                for(var i = result.Count-1; i >= 0; i--)
                {
                    if (result[i].Status != SPModerationStatusType.Approved)
                        result.RemoveAt(i);
                }

            return result;
        }

        /// <summary>
        /// Gets the approved announcements.
        /// </summary>
        /// <param name="limit">The number of announcements to return. 0 means return all.</param>
        /// <returns></returns>
        public PagedResult<AnnouncementDto> GetApprovedAnnouncements(int page, int pageSize)
        {
            var results = new PagedResult<AnnouncementDto>();

            var sec = new SPSecurity.CodeToRunElevated(delegate
            {
                using (var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    results = GetApprovedAnnouncements(web, page, pageSize);
                }
            }
                );
            SPSecurity.RunWithElevatedPrivileges(sec);

            return results;
        }

        private PagedResult<AnnouncementDto> GetApprovedAnnouncements(SPWeb web, int page, int pageSize)
        {
            var result = new PagedResult<AnnouncementDto>();
            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var list = pubWeb.PagesList;
            var ctId = new SPContentTypeId(ContentTypes.Announcement);
            var items = GetApprovedItems(list, ctId, "Created", false);

            if (items == null)
                return result;
            if (items.Count == 0)
                return result;

            result.Page = (page <= 0 ? 1 : page);
            result.PageSize = (pageSize <= 0 ? items.Count : pageSize);
            result.PageCount = (int)Math.Ceiling(items.Count / (decimal)result.PageSize);

            var startRow = Math.Min((result.Page - 1) * result.PageSize, items.Count);

            for (var i = startRow; i < Math.Min(startRow + result.PageSize, items.Count); i++)
            {
                var entry = CreateAnnouncement(items[i]);
                result.Results.Add(entry);
            }

            return result;
        }

        /// <summary>
        /// Gets the pending announcements.
        /// </summary>
        public IList<AnnouncementDto> GetPendingAnnouncements()
        {
            IList<AnnouncementDto> results = new List<AnnouncementDto>();

            var sec = new SPSecurity.CodeToRunElevated(delegate
                  {
                      using(var site = new SPSite(_announcementsSiteUrl))
                      using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                      {
                          results = GetPendingAnnouncements(web);
                      }
                  }                                         
                );
            SPSecurity.RunWithElevatedPrivileges(sec);

            return results;
        }

        private IList<AnnouncementDto> GetPendingAnnouncements(SPWeb web)
        {
            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var list = pubWeb.PagesList;
            var ctId = new SPContentTypeId(ContentTypes.Announcement);
            var items = GetPendingItems(list, ctId, "Created", false);
            var results = CreateAnnouncements(items);
            return results;
        }

        private IList<AnnouncementDto> CreateAnnouncements(SPListItemCollection announcements)
        {
            IList<AnnouncementDto> results = new List<AnnouncementDto>();

            foreach (SPListItem item in announcements)
                results.Add(CreateAnnouncement(item));

            return results;
        }

        public DateTime GetLastModifiedDate()
        {
            using(var site = new SPSite(_announcementsSiteUrl))
            using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
            {
                var queryString = "<OrderBy><FieldRef Name='Modified' Ascending='False' /></OrderBy>";
                var query = new SPQuery {Query = queryString, RowLimit = 1};
                var lastChange = DateTime.MinValue;

                var pubWeb = PublishingWeb.GetPublishingWeb(web);
                var list = pubWeb.PagesList;
                var items = list.GetItems(query);

                if (items != null)
                    if (items.Count > 0)
                        lastChange = (DateTime) items[0]["Modified"];

                list = web.Lists["Comments"];
                items = list.GetItems(query);

                if(items != null)
                    if (items.Count > 0)
                    {
                        var lastComment = (DateTime)items[0]["Modified"];
                        if (lastComment > lastChange)
                            lastChange = lastComment;
                    }

                return lastChange;
            }
        }

        public void SaveComment(CommentDto dto)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate
            { 
                using(var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    web.AllowUnsafeUpdates = true;
                    SaveComment(web, dto);
                }
            });
        }

        private void SaveComment(SPWeb web, CommentDto dto)
        {
            var list = web.Lists["Comments"];
            var item = list.Items.Add();
            item["Comment"] = dto.Body;
            item["Author"] = web.SiteUsers[dto.OwnerUserName];

            var announcement = new SPFieldLookupValue(dto.Parent.Id, dto.Parent.Title);
            item["NewsItem"] = announcement;

            item.Update();
        }
        
        public void SaveAnnouncement(AnnouncementDto dto)
        {
            if (dto == null)
                return;

            using(var site = new SPSite(_announcementsSiteUrl))
            using(var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
            {
                site.AllowUnsafeUpdates = true;
                web.AllowUnsafeUpdates = true;
                SaveAnnouncement(web, dto);
            }
        }

        private void SaveAnnouncement(SPWeb web, AnnouncementDto dto)
        {
            if (dto == null)
            {
                Logger.LogWarning("SaveAnnouncement: cannot save announcement, dto is null");
                return;
            }

            if(web == null)
            {
                Logger.LogWarning("SaveAnnouncement: cannot save announcement, web is null");
                return;
            }

            AddApproverRoleToUser();
            
            web.AllowUnsafeUpdates = true;

            EnableModerationStatus(EnetaApplication.PortalAnnouncementsSiteUrl);

            PublishingPage page;
            if (dto.Id == 0)
                page = CreatePage(web, dto);
            else
                page = GetPage(web, dto.Id);

            if(page == null)
            {
                Logger.LogWarning("SaveAnnouncement: page is null");
                return;
            }

            var item = page.ListItem;
            SPModerationStatusType status = SPModerationStatusType.Draft;
            if(item.ModerationInformation != null)
                status = item.ModerationInformation.Status;
            
            var activity =  new AdminActivityDto();

            if (dto.Id != 0)
                try
                {                
                    activity.PreviousEditor = new SPFieldUserValue(web, item["Editor"].ToString()).User.LoginName;
                    activity.PreviousStatus = status;
                    activity.PreviousTimeStamp = (DateTime)item["Modified"];
                    activity.Url = dto.DisplayUrl;
                    activity.Module = "Announcements";
                }
                catch(Exception ex)
                {
                    Logger.LogInfo("SaveAnnouncement: Exception when creating activity");
                    Logger.LogException(ex);
                }
            
            try
            {
                item.File.CheckOut();
            }
            catch (Exception ex)
            {
                Logger.LogInfo("SaveAnnouncement: Exception when checking file out");
                Logger.LogException(ex);
            }

            var ctId = new SPContentTypeId(ContentTypes.Announcement);
            item["ContentTypeId"] = ctId;
            item[AnnouncementFields.Title] = dto.Title;
            item[AnnouncementFields.Body] = RegExHelper.RemoveForbiddenAttributes(dto.Body);
            //item[AnnouncementFields.RealAuthor] = dto.OwnerUserName;

            if (dto.Keywords != null)
            {
                var values = new SPFieldLookupValueCollection();
                
                foreach (var keyword in dto.Keywords)
                {
                    var value = new SPFieldLookupValue(keyword.Id, keyword.Title);
                    values.Add(value);
                }
                item[AnnouncementFields.Keywords] = values;
            }
            else
            {
                item[AnnouncementFields.Keywords] = null;
            }

            if (dto.Companies != null)
                foreach (CompanyDto company in dto.Companies)
                {
                    item[AnnouncementFields.CompanyId] = company.Id;
                    // we have only one company binded to announcement
                    break;
                }
            else
                item[AnnouncementFields.CompanyId] = null;
            
            item.Update();
            item.File.CheckIn(string.Empty, SPCheckinType.MajorCheckIn);

            if (status == SPModerationStatusType.Approved)
                item.File.Approve(string.Empty);

            if (dto.Id != 0)
            {
                activity.CurrentEditor = new SPFieldUserValue(web, item["Editor"].ToString()).User.LoginName;
                activity.CurrentStatus = item.ModerationInformation.Status;
                activity.CurrentTimeStamp = (DateTime)item["Modified"];
                ActivityLogger.LogActivity(activity);
            }

            RemoveApproverRoleFromUser();

            web.AllowUnsafeUpdates = false;
        }

        private PublishingPage GetPage(SPWeb web, int pageId)
        {
            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var list = pubWeb.PagesList;
            var item = list.GetItemById(pageId);

            return PublishingPage.GetPublishingPage(item);
        }

        private PublishingPage CreatePage(SPWeb web, AnnouncementDto dto)
        {
            var pubWeb = PublishingWeb.GetPublishingWeb(web);
            var ctId = new SPContentTypeId(ContentTypes.Announcement);
            var pageName = UrlHelper.GetPageFileName(dto.Title);
            var layouts = pubWeb.GetAvailablePageLayouts(ctId);

            if (layouts.Length == 0)
                throw new Exception("Cannot find announcements page layout!");

            var newsLayout = layouts[0];

            var list = pubWeb.PagesList;
            var pages = pubWeb.GetPublishingPages();
            var pageUrl = list.RootFolder.ServerRelativeUrl + "/" + pageName;
            var counter = 1;

        checkpage:
            var page = web.GetFile(pageUrl);
            if (page != null)
                if (page.Exists)
                {
                    if (counter == 1)
                        pageUrl = pageUrl.Replace(".aspx", "-1.aspx");
                    else
                        pageUrl = pageUrl.Replace("-" + (counter - 1) + ".aspx", "-" + counter + ".aspx");
                    counter++;
                    goto checkpage;
                }

            var publishingPage = pages.Add(pageUrl, newsLayout);
            return publishingPage;
        }

        private void AddApproverRoleToUser()
        {
            var userId = SPContext.Current.Web.CurrentUser.LoginName;
            SPSecurity.RunWithElevatedPrivileges(delegate
            {
                using (var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    web.AllowUnsafeUpdates = true;
                    var pubWeb = PublishingWeb.GetPublishingWeb(web);
                    var pubList = pubWeb.PagesList;

                    var cuser = web.SiteUsers[userId];

                    var assq = from a in pubList.RoleAssignments.Cast<SPRoleAssignment>()
                               from r in a.RoleDefinitionBindings.Cast<SPRoleDefinition>()
                               where a.Member.Name == cuser.LoginName &&
                                     r.Name == EnetaApplication.PortalApproverRoleName
                               select a;
                    
                    Logger.LogInfo("CUSER: " + cuser);

                    if (assq.Count() == 0)
                    {
                        var ass2 = new SPRoleAssignment(cuser);
                        SPRoleDefinition rb2;

                        try
                        {
                            rb2 = web.RoleDefinitions[EnetaApplication.PortalApproverRoleName];
                        }
                        catch (Exception ex)
                        {
                            foreach(SPRoleDefinition rdef in web.RoleDefinitions)
                            {
                                Logger.LogInfo("ROLEDEF: " + rdef.Name);
                            }

                            Logger.LogException(ex);
                            return;
                        }
                        ass2.RoleDefinitionBindings.Add(rb2);

                        if (!pubList.HasUniqueRoleAssignments)
                        {
                            pubList.BreakRoleInheritance(true);
                            pubList.Update();
                        }

                        pubList.RoleAssignments.Add(ass2);  
                        pubList.Update();
                    }
                    web.AllowUnsafeUpdates = false;
                }
            });
        }

        private void RemoveApproverRoleFromUser()
        {
            var userId2 = SPContext.Current.Web.CurrentUser.LoginName;

            SPSecurity.RunWithElevatedPrivileges(delegate
            {
                using (var site = new SPSite(_announcementsSiteUrl))
                using (var web = site.OpenWeb(GetWebUrl(site, _announcementsSiteUrl)))
                {
                    web.AllowUnsafeUpdates = true;
                    var pubWeb = PublishingWeb.GetPublishingWeb(web);
                    var pubList = pubWeb.PagesList;

                    var cuser = web.SiteUsers[userId2];

                    pubList.RoleAssignments.Remove(cuser);
                    pubList.Update();
                    web.AllowUnsafeUpdates = false;
                }
            });
        }

    }
}